/*
 * Copyright (c) 2010-2020 IISI.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of IISI.
 */
package com.iisigroup.log.analysis.index;

import com.iisigroup.log.analysis.model.StuckThreadRecord;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.MMapDirectory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 

/**
 *
 */
public class Maintainer {
    //================================================
    //== [Enumeration types] Block Start
    //== [Enumeration types] Block End 
    //================================================
    //== [static variables] Block Start
    //== [static variables] Block Stop 
    //================================================
    //== [instance variables] Block Start
    /** 定義版本號碼**/
    private final static Version LUCENE_VER=Version.LUCENE_4_10_1;
    
    /** The Constant LOG. */
    private transient final Logger logger = LoggerFactory.getLogger(getClass());
    
    
    private final  String INDEX_PATH_NAME = "/opt/ramdisk/logAnalysisIndex";
    
    //== [instance variables] Block Stop 
    //================================================
    //== [static Constructor] Block Start
    //== [static Constructor] Block Stop 
    //================================================
    //== [Constructors] Block Start (含init method)
    //== [Constructors] Block Stop 
    //================================================
    //== [Static Method] Block Start
    //== [Static Method] Block Stop 
    //================================================
    //== [Accessor] Block Start
    //== [Accessor] Block Stop 
    //================================================
    //== [Overrided Method] Block Start (Ex. toString/equals+hashCode)
    //== [Overrided Method] Block Stop 
    //================================================
    //== [Method] Block Start
    public void query(final StuckThreadRecord  aRlunlockrecType){

        IndexReader reader = null;
        Directory index = null;
        try { 

            index = getIndex(INDEX_PATH_NAME);
             
            reader = DirectoryReader.open(index);
            final IndexSearcher searcher = new IndexSearcher(reader);

            final Analyzer analyzer = new StandardAnalyzer(LUCENE_VER);

            final Query query = getQueryFromBean(aRlunlockrecType , analyzer);

            final TopDocs topDocs = searcher.search(query, 1000000);
            if (topDocs != null) {
                if (topDocs.totalHits == 0) {
                    logger.debug("topDocs.totalHits = 0");
                } else {
                    final ScoreDoc[] scoreDocs = topDocs.scoreDocs;
                    for (int i = 0; i < topDocs.totalHits; i++) {
                        try {
                            final int docId = scoreDocs[i].doc;
                            final Document document = searcher.doc(docId);
                           convertFromDocument(document);
                            
                        } catch (java.lang.ArrayIndexOutOfBoundsException e) {
                            logger.debug(e.getMessage(), e.getCause());
                        }
                    }
                }
            }
        }   catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
            if (index != null) {
                try {
                    index.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            } 
        }
    }

    protected Query getQueryFromBean(final StuckThreadRecord  aRlunlockrecType , final Analyzer analyzer) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParseException {
        

        // 取得查詢物件屬性
        final PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(aRlunlockrecType);

        final List<String> propertyNameList = new ArrayList<String>();
        final List<String> propertyValueList = new ArrayList<String>();
        for (java.beans.PropertyDescriptor theDesc : desc) {
            final String descPropertyName = theDesc.getName();
            final Class<?> type = theDesc.getPropertyType();

            if (type.equals(String.class)) {
                String value = (String) PropertyUtils.getProperty(aRlunlockrecType, descPropertyName);
                if (StringUtils.isNotBlank(value) && !StringUtils.contains(value, ":")) {
                    propertyNameList.add(descPropertyName);
                    value = StringUtils.substringBefore(value, "!");
                    propertyValueList.add(value);
                }
            }
        }

        // 將查詢物件屬性值轉成查詢條件
        final String[] query = propertyValueList.toArray(new String[] {});

        // 將查詢物件屬性名稱值轉成查詢條件的名稱,有點像table schema的欄位名稱
        final String[] fields = propertyNameList.toArray(new String[] {});

        logger.debug("queries: {} ,fields: {}", StringUtils.join(query, ":"), StringUtils.join(fields, ":"));

        // 相當SQL的等於,不等於的意思
        BooleanClause.Occur[] flags = new BooleanClause.Occur[query.length];

        for (int i = 0; i < query.length; ++i) {
            flags[i] = BooleanClause.Occur.SHOULD;
        }
        // 有的queryParser有支援類似SQL的syntax
        final Query result = MultiFieldQueryParser.parse(LUCENE_VER, query, fields, flags, analyzer);

        return result;
    }
    protected void convertFromDocument(final Document document) {

        final List<IndexableField> fileds = document.getFields();
        for (IndexableField filed : fileds) {
            final String propertyName = filed.name();
            final String value = document.get(propertyName);
            logger.debug("name: {} value: {}", propertyName, value);

        }
    }
    public void storeCache(final StuckThreadRecord... data) {
        IndexWriter indexWriter = null;
        try {            
            indexWriter = getIndexWriter(INDEX_PATH_NAME);
            for(StuckThreadRecord unit : data){
                addDoc(indexWriter, unit);
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (indexWriter != null) {
                try {
                    indexWriter.close();
                } catch (IOException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    protected IndexWriter getIndexWriter(final String filePath) throws IOException  {
        // 1. create the index
        final Directory index = getIndex(filePath);
        final IndexWriter indexWriter = getIndexWriter(index);
        return indexWriter;

    }
    protected IndexWriter getIndexWriter(final Directory index) throws IOException {
        // 0. Specify the analyzer for tokenizing text.
        // The same analyzer should be used for indexing and searching
        final StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VER);

        final IndexWriterConfig config = new IndexWriterConfig(LUCENE_VER, analyzer);

        final IndexWriter indexWriter = new IndexWriter(index, config);
        return indexWriter;
    }
    protected Directory getIndex(final String filePath) throws IOException  {        
        final File usrpath = new File (filePath);
        if (!usrpath.exists() || !usrpath.isDirectory()) {
            usrpath.mkdir();
        }

        // 1. create the index
        final Directory index = new MMapDirectory(usrpath);

        return index;
    }
    protected void addDoc(final IndexWriter writer, final StuckThreadRecord str) throws IOException {
        final String stackTrace = str.getStackTrace();
        final String hostIp = str.getHostIp();
        final String date = str.getDate();
        final String time = str.getTime();
        final String clientIp = str.getClientIp();
        final String userId =  str.getUserId();

        final Document doc = new Document();

        doc.add(new TextField("stackTrace", stackTrace, Field.Store.YES));
        
        if(hostIp != null){
            doc.add(new TextField("hostIp", hostIp, Field.Store.YES));
        }
        if(userId != null){
            // use a string field for clientIp because we don't want it tokenized
            doc.add(new TextField("userId", userId, Field.Store.YES));
        }

        // use a string field for date because we don't want it tokenized
        doc.add(new StringField("date", date, Field.Store.YES));

        // use a string field for time because we don't want it tokenized
        doc.add(new StringField("time", time, Field.Store.YES));
        
        if(clientIp != null){
            // use a string field for clientIp because we don't want it tokenized
            doc.add(new StringField("clientIp", clientIp, Field.Store.YES));
        }
        writer.addDocument(doc);
    }
    //####################################################################
    //## [Method] sub-block : 
    //####################################################################    
    //== [Method] Block Stop 
    //================================================
    //== [Inner Class] Block Start
    //== [Inner Class] Block Stop 
    //================================================
}
